package logic.common.team;

import interfaces.spawnMenu.squads.SquadsWindow;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import networking.messages.teamMessages.TeamValueMessage;

import logic.common.Squad;
import logic.common.game.Game;
import logic.common.missions.Mission;
import logic.common.player.Player;
import logic.common.player.PlayerComparator;
import logic.common.team.processes.ProcessAddEvent;
import logic.common.team.processes.ProcessFinishEvent;
import logic.common.team.processes.ProcessStartEvent;
import logic.common.team.processes.TeamProcess;
import logic.common.team.processes.TeamProcessListener;
import logic.common.team.teamListeners.DroneKillEvent;
import logic.common.team.teamListeners.MissionAddedEvent;
import logic.common.team.teamListeners.MissionCompletedEvent;
import logic.common.team.teamListeners.MissionDeletedEvent;
import logic.common.team.teamListeners.ShipAddedEvent;
import logic.common.team.teamListeners.ShipRemovedEvent;
import logic.common.team.teamListeners.TeamListener;
import logic.common.team.teamListeners.TeamMissionListener;
import logic.common.team.teamListeners.TeamShipListener;
import logic.common.team.teamListeners.TeamValueChangedEvent;
import logic.common.team.teamListeners.TeamValueType;
import logic.items.ItemFactory;
import logic.items.ItemProperties;
import logic.items.ItemType;
import logic.nodes.TeamNode;
import logic.nodes.collision.DummyType;
import logic.nodes.nodeSettings.Settings;
import logic.nodes.nodeSettings.upgrades.UpgradableSettings;
import logic.ships.MissionShip;
import logic.ships.autoWeaponShip.AutoWeaponShipProperties;
import logic.ships.carrier.Carrier;
import logic.ships.drone.Drone;
import logic.ships.drone.DroneProperties;
import logic.ships.frigate.Frigate;
import logic.ships.hunter.Hunter;
import logic.ships.mothership.MotherShip;
import logic.ships.moveableShip.MovableShipProperties;
import logic.ships.resourceShip.ResourceShip;
import logic.weapons.Weapon;
import logic.weapons.WeaponProperties;
import logic.weapons.WeaponType;
import logic.weapons.projectileWeapons.heatSeeker.HeatSeekerProperties;
import logic.weapons.projectileWeapons.missile.MissileProperties;

import ai.TeamAIController;

import com.jme.math.Vector3f;
import com.jme.scene.Node;

import factories.HunterFactory;
import factories.MovableShipFactory;
import factories.WeaponFactory;
import fileHandling.FolderManager;
import fileHandling.ModelImporter;
import fileHandling.SettingsLoader;
import gameStates.absGamesStates.AbsIngameState;

import main.InitGame;
import main.listeners.ListenerContainer;
import main.listeners.ListenerKeeper;
import map.asteroids.AsteroidField;
import map.infos.TeamInfos;
import map.spawnStation.SpawnPosition;
import map.spawnStation.SpawnStation;

public class Team implements ListenerKeeper {
	
	public static final int MAX_FRIGATES = 1;
	
	public static final String MS_ENERGY_WEAPON = "energy";
	public static final String MS_HEATSEEKER_WEAPON = "heatseeker";
	public static final String MS_ARTILLARY_WEAPON = "artillary";
	
	public static final String FRIGATE_ENERGY_WEAPON = "energy";
	public static final String FRIGATE_HEATSEEKER_WEAPON = "heatseeker";
	public static final String FRIGATE_MISSILE_WEAPON = "missile";
	
	public static final float MAX_HARVEST_DIST = 250f;
	
	private TeamInfos teamInfos;
	private Fraction fraction;
	
	private List<Player> allPlayers, bots, humanPlayers;
	
	private List<SpawnStation> spawnStations;
	private List<SpawnPosition> spawnPositions;
	
	private MotherShip motherShip;
	private Vector3f motherShipSpawn;
	
	private List<String> availableHunterNames, energyWeaponNames, heatseakerNames, missileNames;
	
	private Settings maxWeaponValues, maxHunterValues;
	private int pilotMoney, teamScore, tickets;
	
	private List<Frigate> frigates;
	private List<Carrier> carriers;
	private List<Drone> drones;
	
	private List<TeamNode> openFrigateTargets;
	
	private Map<Integer, Squad> squads;
	private LinkedList<Squad> nonEmptySquads;
	private List<Squad> allSquads;
	
	private DroneProperties droneProps;
	
	private HashMap<WeaponType, WeaponProperties> msWeaponProperties, frigateWeaponProperties;
	
	private HashMap<ItemType, ItemProperties> itemProperties;
	private List<ItemProperties> itemPropList;
	
	private Game game;
	
	private float teamMoney;
	private ListenerContainer teamListeners, teamShipListeners;
	
	private List<AsteroidField> conqueredFields, carrierFreeFields;
	private HashMap<AsteroidField, ResourceShip> fieldShipMap;
	private HashMap<SpawnStation, AsteroidField> stationFieldMap;
	
	private TeamAIController teamAI;
	
	private AbsIngameState ingameState;
	
	private List<Mission> missions;
	private ListenerContainer missionListeners;
	private HashMap<TeamNode, Collection<Mission>> targetMissionMap;
	
	private ListenerContainer teamProcessListeners;
	private LinkedList<TeamProcess> teamProcesses;
	private TeamProcess activeTeamProcess;
	
	private Team enemyTeam;
	
	public Team(Fraction fraction, TeamInfos teamInfos, int pilotMoney, AbsIngameState ingameState) {
		this.fraction = fraction;
		this.teamInfos = teamInfos;
		this.pilotMoney = pilotMoney;
		this.ingameState = ingameState;
		
		allPlayers = new ArrayList<Player>();
		bots = new ArrayList<Player>();
		humanPlayers = new ArrayList<Player>();
		
		spawnStations = new ArrayList<SpawnStation>();
		spawnPositions = new ArrayList<SpawnPosition>();
		
		setWeaponNames();
		setHunterNames();
		
		drones = new ArrayList<Drone>();
		getDroneProperties();
		
		carriers = new ArrayList<Carrier>();
		frigates = new ArrayList<Frigate>();
		
		openFrigateTargets = new ArrayList<TeamNode>();
		squads = new HashMap<Integer, Squad>();
		allSquads = new ArrayList<Squad>();
		nonEmptySquads = new LinkedList<Squad>();
		
		for(int i = 0; i < SquadsWindow.MAX_SQUADS; i++) {
			Integer squadIndex = Integer.valueOf(i);
			Squad squad = new Squad(this, i);
			squads.put(squadIndex, squad);
			allSquads.add(squad);
			nonEmptySquads.add(squad);
		}
		
		getMSWeaponProperties();
		getFrigateWeaponProperties();
		getItemProperties();
		
		teamListeners = new ListenerContainer(TeamListener.class);
		teamShipListeners = new ListenerContainer(TeamShipListener.class);
		
		conqueredFields = new ArrayList<AsteroidField>();
		carrierFreeFields = new ArrayList<AsteroidField>();
		fieldShipMap = new HashMap<AsteroidField, ResourceShip>();
		
		stationFieldMap = new HashMap<SpawnStation, AsteroidField>();
		
		if(InitGame.get().isServer()) teamAI = new TeamAIController(this);
		
		missions = new ArrayList<Mission>();
		
		missionListeners = new ListenerContainer(TeamMissionListener.class);
		
		targetMissionMap = new HashMap<TeamNode, Collection<Mission>>();
		
		teamProcessListeners = new ListenerContainer(TeamProcessListener.class);
		teamProcesses = new LinkedList<TeamProcess>();
	}
	
	public void addTeamProcessListener(TeamProcessListener l) { teamProcessListeners.addListener(l); }
	
	public void addTeamProcess(final TeamProcess process) {
		ingameState.getThreadPool().addPostRunnable(new Runnable() {
			@Override
			public void run() {
				if(activeTeamProcess == null) setActiveTeamProcess(process);
				else {
					teamProcesses.add(process);
					teamProcessListeners.callMethod(TeamProcessListener.PROCESSADDED, new ProcessAddEvent(process));
				}
			}
		});
	}
	
	public void setActiveTeamProcess(final TeamProcess process) {
		ingameState.getThreadPool().addPostRunnable(new Runnable() {
			@Override
			public void run() {
				activeTeamProcess = process;
				
				teamProcessListeners.callMethod(TeamProcessListener.PROCESSSTARTED, new ProcessStartEvent(process));
			}
		});
	}
	
	public void teamProcessFinished(final TeamProcess process) {
		ingameState.getThreadPool().addPostRunnable(new Runnable() {
			@Override
			public void run() {
				assert(activeTeamProcess == process);
				
				TeamProcess p = teamProcesses.poll();
				if(p != null) setActiveTeamProcess(p);
				else activeTeamProcess = null;
				
				teamProcessListeners.callMethod(TeamProcessListener.PROCESSFINISHED, new ProcessFinishEvent(process));
			}
		});
	}
	
	public LinkedList<TeamProcess> getPendingTeamProcesses() { return teamProcesses; }
	
	public TeamProcess getActiveTeamProcess() { return activeTeamProcess; }
	
	public Squad getSquad(int index) { return allSquads.get(index); }
	
	public Squad getNextNonFullSquad() {
		return nonEmptySquads.peek();
	}
	
	public void pushNoneEmptySquad(Squad squad) {
		assert(!squad.isFull());
		if(!nonEmptySquads.contains(squad)) nonEmptySquads.push(squad);
	}
	
	public void removeFromEmptySquad(Squad squad) { nonEmptySquads.remove(squad); }
	
	public Iterator<Squad> getAllSquadIterator() { return allSquads.iterator(); }
	
	public void setTickets(int tickets) { this.tickets = tickets; }
	
	public int getTickets() { return tickets; }
	
	public void decreaseTickets() {
		updateTickets(tickets - 1);
	}
	
	private void updateTickets(int newTickets) {
		tickets = newTickets;
		
		TeamValueChangedEvent event = new TeamValueChangedEvent(this, TeamValueType.Tickets, tickets);
		teamListeners.callMethod(TeamListener.TEAMVALUECHANGED, event);
	}
	
	public void increaseScore() {
		updateScore(teamScore + 1);
	}
	
	private void updateScore(int newScore) {
		teamScore = newScore;
		
		TeamValueChangedEvent event = new TeamValueChangedEvent(this, TeamValueType.Score, teamScore);
		teamListeners.callMethod(TeamListener.TEAMVALUECHANGED, event);
	}
	
	public int getTeamScore() { return teamScore; }
	
	public List<TeamNode> getOpenFrigateTargets() { return openFrigateTargets; }
	
	public Team getEnemyTeam() { return enemyTeam; }
	
	private void addFrigateTarget(TeamNode target) {
		openFrigateTargets.add(target);
		
		if(target instanceof MissionShip) {
			MissionShip missionShip = (MissionShip)target;
			Collection<Mission> missions = missionShip.getTargetMissions(this);
			targetMissionMap.put(target, missions);
			
			for(Mission m : missions) {
				addMission(m);
			}
		}
	}
	
	private void removeFrigateTarget(TeamNode target) {
		openFrigateTargets.remove(target);
		
		Collection<Mission> missions = targetMissionMap.remove(target);		
		if(missions != null) {
			for(Mission m : missions) {
				deleteMission(m);
			}
		}
	}
	
	public void setEnemyTeam(Team team) {
		enemyTeam = team;
		enemyTeam.addTeamShipListener(new TeamShipListener() {
			@Override
			public void shipAdded(ShipAddedEvent event) {
				addFrigateTarget(event.getShip());
			}

			@Override
			public void shipRemoved(ShipRemovedEvent event) {
				removeFrigateTarget(event.getShip());
			}
		});
	}
	
	public void addMissionListener(TeamMissionListener l) { missionListeners.addListener(l); }
	
	public void removeMissionListener(TeamMissionListener l ) { missionListeners.removeListener(l); }
	
	public List<Mission> getMissions() { return missions; }
	
	public void addMission(Mission mission) {
		missions.add(mission);
		
		MissionAddedEvent event = new MissionAddedEvent(mission);
		missionListeners.callMethod(TeamMissionListener.MISSIONADDED, event);
	}
	
	public void deleteMission(Mission mission) {
		if(!missions.remove(mission)) return;
		
		MissionDeletedEvent event = new MissionDeletedEvent(mission);
		missionListeners.callMethod(TeamMissionListener.MISSIONDELETED, event);
	}
	
	public void missionAccomplished(Mission mission, Player player) {
		if(mission.deleteAfterComplete()) missions.remove(mission);
		
		player.addMoney(mission.getRewardMoney());
		
		MissionCompletedEvent event = new MissionCompletedEvent(mission, player);
		missionListeners.callMethod(TeamMissionListener.MISSIONCOMPLETED, event);
	}
	
	public void initTeamAIController() {
		teamAI.initControllers();
		getIngameState().getThreadPool().registerController(teamAI);
	}
	
	public void addTeamListener(TeamListener l) { teamListeners.addListener(l); }
	
	public void removeTeamListener(TeamListener l) { teamListeners.removeListener(l); }
	
	public void addTeamShipListener(TeamShipListener l) { teamShipListeners.addListener(l); }
	
	public void removeTeamShipListener(TeamShipListener l) { teamShipListeners.removeListener(l); }
	
	private void setHunterNames() {
		availableHunterNames = new ArrayList<String>();
		maxHunterValues = new Settings();
		String shipsPath = "data/fractions/" + fraction.toString().toLowerCase() + "/ships";
		File f = new File(shipsPath);
		File[] fileArray = f.listFiles();
		for(int i = 0; i < fileArray.length; i++) {
			if(fileArray[i].isDirectory() && !fileArray[i].getPath().contains(".") 
			&& !fileArray[i].getName().equals("mothership") 
			&& !fileArray[i].getName().equals("drone") 
			&& !fileArray[i].getName().equals("carrier") 
			&& !fileArray[i].getName().equals("frigate")) {
				availableHunterNames.add(fileArray[i].getName());
				addHunterSettings(fileArray[i].getName());
			}
		}
	}
	
	public String getMSWeaponName(WeaponType type) {
		switch(type) {
			case Artillary: return MS_ARTILLARY_WEAPON;
			case Energy: return MS_ENERGY_WEAPON;
			case HeatSeeker: return MS_HEATSEEKER_WEAPON;
		}
		return null;
	}
	
	private void getMSWeaponProperties() {
		msWeaponProperties = new HashMap<WeaponType, WeaponProperties>();
		String msWeaponPath = "data/fractions/" + fraction.toString().toLowerCase() + "/motherShipWeapons/";
		
		String energyPath = msWeaponPath + MS_ENERGY_WEAPON + "/";
		msWeaponProperties.put(WeaponType.Energy, WeaponFactory.getWeaponProperties(energyPath));
		
		String artillaryPath = msWeaponPath + MS_ARTILLARY_WEAPON + "/";
		msWeaponProperties.put(WeaponType.Artillary, WeaponFactory.getWeaponProperties(artillaryPath));
		
		String heatSeekerPath = msWeaponPath + MS_HEATSEEKER_WEAPON + "/";
		msWeaponProperties.put(WeaponType.HeatSeeker, WeaponFactory.getWeaponProperties(heatSeekerPath));
	}
	
	private void getFrigateWeaponProperties() {
		frigateWeaponProperties = new HashMap<WeaponType, WeaponProperties>();
		String frigateWeaponPath = "data/fractions/" + fraction.toString().toLowerCase() + "/frigateWeapons/";
		
		String energyPath = frigateWeaponPath + FRIGATE_ENERGY_WEAPON + "/";
		frigateWeaponProperties.put(WeaponType.Energy, WeaponFactory.getWeaponProperties(energyPath));
		
		String missilePath = frigateWeaponPath + FRIGATE_MISSILE_WEAPON + "/";
		frigateWeaponProperties.put(WeaponType.Missile, WeaponFactory.getWeaponProperties(missilePath));
		
		String heatSeekerPath = frigateWeaponPath + FRIGATE_HEATSEEKER_WEAPON + "/";
		frigateWeaponProperties.put(WeaponType.HeatSeeker, WeaponFactory.getWeaponProperties(heatSeekerPath));
	}
	
	public HashMap<ItemType, ItemProperties> getItemProperties() {
		if(itemProperties != null) return itemProperties;
		itemProperties = new HashMap<ItemType, ItemProperties>();
		
		String itemPath = FolderManager.fractionDir(fraction, "items");
		File itemDirFile = new File(itemPath);
		File[] subFiles = itemDirFile.listFiles();
		for(File f : subFiles) {
			if(!f.isDirectory()) {
				UpgradableSettings settings = SettingsLoader.loadUpgradableSettings(f.getPath());
				ItemProperties props = ItemFactory.createItemProps(settings);
				itemProperties.put(props.getType(), props);
			}
		}
		
		itemPropList = new ArrayList<ItemProperties>();
		itemPropList.add(itemProperties.get(ItemType.Repair));
		itemPropList.add(itemProperties.get(ItemType.Shields));
		itemPropList.add(itemProperties.get(ItemType.Energy));
		
		return itemProperties;
	}
	
	public ItemProperties getItemProp(ItemType type) { return itemProperties.get(type); }
	
	public List<ItemProperties> getItemPropList() { return itemPropList; }
	
	public Hunter getNearestHunter(Vector3f startLoc) {
		Hunter nearestHunter = null;
		float minDist = Float.POSITIVE_INFINITY;
		
		for(Player player : allPlayers) {
			Hunter hunter = player.getHunter();
			
			if(player.isAlive() && hunter != null) {
				float dist = hunter.getLocalTranslation().distance(startLoc);
				if(dist < minDist) {
					minDist = dist;
					nearestHunter = hunter;
				}
			}
		}
		
		return nearestHunter;
	}
	
	private void addHunterSettings(String hunterName) {
		String fraction = this.fraction.toString().toLowerCase();
		String hunterPath = "data/fractions/" + fraction + "/ships/" + hunterName + "/";
		MovableShipProperties props = HunterFactory.getMovableShipProperties(hunterPath);
		checkSettings(props, maxHunterValues);
	}
	
	private void checkSettings(UpgradableSettings props, Settings settings) {
		for(String maxKey : props.getKeys()) {
			try {
				String maxString = settings.getValueOf(maxKey);
				if(maxString == null) maxString = "0f";
				float maxValue = Float.valueOf(maxString).floatValue();
				float newValue = Float.valueOf(props.getMaxValueOf(maxKey)).floatValue();
				if(maxValue < newValue) settings.addOption(maxKey, newValue + "");
			} catch(Exception e) {}
		}
	}
	
	private void setWeaponNames() {
		energyWeaponNames = new ArrayList<String>();
		heatseakerNames = new ArrayList<String>();
		missileNames = new ArrayList<String>();
		maxWeaponValues = new Settings();
		
		String weaponsPath = "data/fractions/" + fraction.toString().toLowerCase() + "/hunterWeapons/";
		File f = new File(weaponsPath);
		File[] fileArray = f.listFiles();
		for(int i = 0; i < fileArray.length; i++) {
			File file = fileArray[i];
			if(file.isDirectory() && !file.getPath().contains(".")) {
				WeaponProperties prop = WeaponFactory.getWeaponProperties(weaponsPath + file.getName() + "/");
				if(prop instanceof HeatSeekerProperties) heatseakerNames.add(file.getName());
				else if(prop instanceof MissileProperties) missileNames.add(file.getName());
				else energyWeaponNames.add(file.getName());
				checkSettings(prop, maxWeaponValues);
			}
		}
	}
	
	public List<AsteroidField> getConqueredAsteroidFields() { return conqueredFields; }
	
	public List<AsteroidField> getCarrierFreeFields() { return carrierFreeFields; }
	
	public Collection<AsteroidField> getHarvestableAsteroidFields() { return fieldShipMap.keySet(); }
	
	public ResourceShip getShipAtField(AsteroidField field) { return fieldShipMap.get(field); }
	
	public void updateConqueredFields() {
		if(motherShip == null) return;
		
		for(AsteroidField field : game.getMap().getAsteroidFields()) {
			if(isHarvestable(field)) {
				addConqueredField(field);
				fieldShipMap.put(field, motherShip);
			}
		}
	}
	
	private void addConqueredField(AsteroidField field, SpawnStation station) {
		addConqueredField(field);
		stationFieldMap.put(station, field);
	}
	
	private void addConqueredField(AsteroidField field) {
		assert(field.isResourceField());
		
		conqueredFields.add(field);
	}
	
	public void carrierMovesTo(AsteroidField field) {
		carrierFreeFields.remove(field);
	}
	
	public void carrierArrived(AsteroidField field, Carrier carrier) {
		fieldShipMap.put(field, carrier);
	}
	
	public boolean isHarvestable(AsteroidField field) {
		if(!field.isResourceField()) return false;
		
		Vector3f fieldLoc = field.getProperties().getCenter();
		
		float msDist = motherShip.getLocalTranslation().distance(fieldLoc);
		return msDist <= MAX_HARVEST_DIST;
	}
	
	public void setMotherShip(MotherShip ship, Vector3f motherShipSpawn) {
		assert(ship != null && motherShipSpawn != null);
		
		motherShip = ship;
		this.motherShipSpawn = motherShipSpawn;
		motherShip.getLocalTranslation().set(motherShipSpawn);
		spawnPositions.add(motherShip);
	}
	
	public void addPlayer(Player newPlayer) {
		assert(!allPlayers.contains(newPlayer));
		
		allPlayers.add(newPlayer);
		
		if(newPlayer.isHuman()) humanPlayers.add(newPlayer);
		else bots.add(newPlayer);
		
		teamListeners.callMethod(TeamListener.PLAYERADDED, newPlayer);
	}
	
	public void removePlayer(Player player) {
		assert(player != null);
		assert(allPlayers.contains(player));
		
		allPlayers.remove(player);
		
		if(player.isHuman()) humanPlayers.remove(player);
		else bots.remove(player);
		
		teamListeners.callMethod(TeamListener.PLAYERREMOVED, player);
	}
	
	public Fraction getFraction() { return fraction; }
	
	public List<Player> getSortedPlayers() {
		Collections.sort(allPlayers, PlayerComparator.get());
		return allPlayers;
	}
	
	public List<Player> getAllPlayers() { return allPlayers; }
	
	public List<Player> getBots() { return bots; }
	
	public List<Player> getHumanPlayers() { return humanPlayers; }
	
	public boolean hasPlayer(Player player) {
		assert(player != null);
		return allPlayers.contains(player);
	}
	
	public int getPlayerNumber() { return allPlayers.size(); }
	
	public int getKills() {
		int kills = 0;
		for(Player player : allPlayers) {
			kills += player.getKills();
		}
		return kills;
	}
	
	public int getDeaths() {
		int deaths = 0;
		for(Player player : allPlayers) {
			deaths += player.getDeaths();
		}
		return deaths;
	}
	
	public void setGame(Game game) {
		this.game = game;
	}
	
	public Game getGame() { return game; }
	
	public int getPilotMoney() { return pilotMoney; }
		
	public MotherShip getMotherShip() { return motherShip; }
	
	public float getMSOrientationAngle() { return teamInfos.getMSSpawnAngle(); }
	
	public Vector3f getMotherShipSpawn() { return motherShipSpawn; }
	
	public List<String> getAvailableHunters() { return availableHunterNames; }
	
	public List<String> getEnergyWeaponNames() { return energyWeaponNames; }
	
	public List<String> getHeatseakerNames() { return heatseakerNames; }
	
	public List<String> getMissileNames() { return missileNames; }
	
	public void addTeamMoney(float amount) {
		assert(amount > 0);
		
		updateTeamMoney(teamMoney + amount);
	}
	
	private void updateTeamMoney(float newMoney) {
		if(teamMoney == newMoney) return;
		
		teamMoney = newMoney;
		
		assert(teamMoney >= 0f);
		
		TeamValueChangedEvent event = new TeamValueChangedEvent(this, TeamValueType.Money, (int)teamMoney);
		teamListeners.callMethod(TeamListener.TEAMVALUECHANGED, event);
	}
	
	public void spendTeamMoney(int amount) {
		assert(amount > 0);
		assert(teamMoney >= amount);
		
		updateTeamMoney(teamMoney - amount);
	}
	
	public float getTeamMoney() { return teamMoney; }
	
	private void updateConqueredSpawnStations(SpawnStation newStation) {
		for(AsteroidField field : game.getMap().getAsteroidFields()) {
			if(!field.isResourceField()) continue;
			
			Vector3f fieldLoc = field.getProperties().getCenter();
			if(fieldLoc.distance(newStation.getLocalTranslation()) <= MAX_HARVEST_DIST) {
				addConqueredField(field, newStation);
				carrierFreeFields.add(field);
				break;
			}
		}
	}
	
	public void addSpawnStation(SpawnStation station) {
		spawnStations.add(station);
		spawnPositions.add(station);
		updateConqueredSpawnStations(station);
	}
	
	public void removeSpawnStation(SpawnStation station) {
		spawnStations.remove(station);
		spawnPositions.remove(station);
		
		AsteroidField field = stationFieldMap.remove(station);
		if(field != null) {
			conqueredFields.remove(field);
			carrierFreeFields.remove(field);
		}
	}
	
	public List<SpawnStation> getSpawnStations() { return spawnStations; }
	
	public List<SpawnPosition> getSpawnPositions() { return spawnPositions; }
		
	public SpawnPosition getRandomSpawnPosition() {
		try {
			int locations = spawnPositions.size();
			
			Random r = new Random();
			int index = r.nextInt(locations);
			SpawnPosition pos = spawnPositions.get(index);
			return pos;
		} catch(Exception e) {
			return spawnPositions.get(0);
		}
	}
	
	public Settings getMaxHunterValues() { return maxHunterValues; }
	
	public Settings getMaxWeaponValues() { return maxWeaponValues; }
	
	public void removeDrone(Drone drone, Player player) {
		assert(player != null);
		removeDrone(drone);
		
		DroneKillEvent event = new DroneKillEvent(this, drone, player);
		teamListeners.callMethod(TeamListener.DRONEDESTROYED, event);
	}
	
	public void removeDrone(Drone drone) { drones.remove(drone); }
	
	public void removeCarrier(Carrier carrier) {
		carriers.remove(carrier);
		
		ShipRemovedEvent event = new ShipRemovedEvent(this, carrier);
		teamShipListeners.callMethod(TeamShipListener.SHIPREMOVED, event);
	}
	
	public void removeFrigate(Frigate frigate) {
		frigates.remove(frigate);
		
		ShipRemovedEvent event = new ShipRemovedEvent(this, frigate);
		teamShipListeners.callMethod(TeamShipListener.SHIPREMOVED, event);
	}
	
	public WeaponProperties getMSWeaponProperties(WeaponType type) {
		return msWeaponProperties.get(type);
	}
	
	public WeaponProperties getFrigateWeaponProperties(WeaponType type) {
		return frigateWeaponProperties.get(type);
	}
	
	public List<Drone> getDrones() { return drones; }
	
	public Drone addNewDrone() {
		return addNewDrone(DummyType.None);
	}
	
	public Drone addNewDrone(DummyType dummyType) {
		if(droneProps == null) getDroneProperties();
		
		String modelPath = droneProps.getModelPath();
		Node droneModel = ModelImporter.getModel(modelPath);
		if(droneModel == null) return null;
		
		Drone drone = new Drone(modelPath, "drone", droneModel, droneProps, this, dummyType);
		drones.add(drone);
		if(dummyType == DummyType.None) InitGame.get().getNetworkState().registerSyncObject(drone);
		
		return drone;
	}
	
	public DroneProperties getDroneProperties() {
		if(droneProps != null) return droneProps;
		String fractionName = fraction.toString().toLowerCase();
		String dronePath = "data/fractions/" + fractionName + "/ships/drone/";
		UpgradableSettings settings = SettingsLoader.loadUpgradableSettings(dronePath + "properties.xml");
		droneProps = new DroneProperties(settings);
		return droneProps;
	}
	
	public Frigate addNewFrigate() {
		return addNewFrigate(DummyType.None);
	}
	
	public Frigate addNewFrigate(DummyType dummyType) {
		AutoWeaponShipProperties props = getNewFrigateProperties();
		if(props == null) return null;
		
		String modelPath = props.getModelPath();
		Node model = ModelImporter.getModel(modelPath);
		if(model == null) return null;
		
		Frigate frigate = new Frigate(modelPath, "frigate", model, props, this, dummyType);
		frigates.add(frigate);
		
		ShipAddedEvent event = new ShipAddedEvent(this, frigate);
		teamShipListeners.callMethod(TeamShipListener.SHIPADDED, event);
		
		if(dummyType == DummyType.None) {
			InitGame.get().getNetworkState().registerSyncObject(frigate);
			
			addRandomWeapons(frigate, FRIGATE_ENERGY_WEAPON, WeaponType.Energy);
			addRandomWeapons(frigate, FRIGATE_MISSILE_WEAPON, WeaponType.Missile);
		}
		
		return frigate;
	}
	
	private void addRandomWeapons(Frigate frigate, String weaponName, WeaponType type) {
		int maxSlots = frigate.getWeapontSlotsNumber(type);
		WeaponProperties props = getFrigateWeaponProperties(type);
		
		for(int i = 0; i < maxSlots; i++) {
			Weapon w = WeaponFactory.createNewWeapon(weaponName, frigate, props);
			if(w == null) return;
			frigate.addWeapon(w);
		}
	}
	
	public List<Frigate> getFrigates() { return frigates; }
	
	public Carrier addNewCarrier() {
		return addNewCarrier(DummyType.None);
	}
	
	public Carrier addNewCarrier(DummyType dummyType) {
		MovableShipProperties properties = getNewCarrierProperties();
		if(properties == null) return null;
		
		String modelPath = properties.getModelPath();
		Node carrierModel = ModelImporter.getModel(modelPath);
		if(carrierModel == null) return null;
		
		Carrier carrier = new Carrier(modelPath, "carrier", carrierModel, properties, this, dummyType);
		carriers.add(carrier);
		
		ShipAddedEvent event = new ShipAddedEvent(this, carrier);
		teamShipListeners.callMethod(TeamShipListener.SHIPADDED, event);
		
		if(dummyType == DummyType.None) InitGame.get().getNetworkState().registerSyncObject(carrier);
		
		return carrier;
	}
	
	public List<Carrier> getCarriers() { return carriers; }
	
	public MovableShipProperties getNewCarrierProperties() {
		String fractionName = fraction.toString().toLowerCase();
		String carrierPath = "data/fractions/" + fractionName + "/ships/carrier/";
		
		MovableShipProperties props =  MovableShipFactory.getMovableShipProperties(carrierPath);
		return props;
	}
	
	public AutoWeaponShipProperties getNewFrigateProperties() {
		String fractionName = fraction.toString().toLowerCase();
		String frigatePath = "data/fractions/" + fractionName + "/ships/frigate/";
		
		AutoWeaponShipProperties props =  new AutoWeaponShipProperties(MovableShipFactory.getMovableShipProperties(frigatePath));
		return props;
	}
	
	public AbsIngameState getIngameState() { return ingameState; }
	
	public void applyTeamValueMessage(TeamValueMessage tvm) {
		int value = tvm.getValue();
		
		switch(tvm.getType()) {
			case Money: updateTeamMoney(value); break;
			case Score: updateScore(value); break;
			case Tickets: updateTickets(value); break;
		}
	}
	
	@Override
	public int hashCode() { return fraction.hashCode(); }
	
	@Override
	public ListenerContainer getListenerContainer(Class<?> listenerClass) {
		if(listenerClass == TeamListener.class) return teamListeners;
		if(listenerClass == TeamMissionListener.class) return missionListeners;
		if(listenerClass == TeamShipListener.class) return teamShipListeners;
		if(listenerClass == TeamProcessListener.class) return teamProcessListeners;
		return null;
	}
}